package com.hup.htts.service;

import com.hup.htts.model.MainConfig;
import com.hup.utils.commons.ListUtil;
import com.hup.utils.commons.start.DialogHelper;
import com.hup.utils.commons.thread.component.PrefixAbleThreadFactory;
import com.hup.utils.commons.thread.component.SimpleRejectedHandler;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinUser;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;

import java.awt.AWTException;
import java.awt.Robot;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 全局按键监听[Esc,ctrl+c]
 * 缩减自hup-jna的GlobalHotKeyListener
 *
 * @author hugan
 * @date 2021/9/12
 */
@Log4j2
public class GlobalKeyService {
    /**
     * 每隔n毫秒轮询一次键盘状态
     */
    private static final int CHECK_KEY_SPAN = 100;

    private final MainConfig mainConfig;
    private final ThreadPoolExecutor executor;
    private Robot robot;
    @Setter
    private KeyHandler keyHandler;
    private boolean listening = false;
    /**
     * 上次开始执行action的时间
     */
    private Long lastActTime;
    /**
     * 重复激活快捷键回调的最小间隔ms (少于间隔的事件将被忽略)
     */
    private static final int minActSpan = 500;

    public GlobalKeyService(MainConfig mainConfig) {
        this.mainConfig = mainConfig;
        //log.info("config={}", mainConfig);
        executor = new ThreadPoolExecutor(
                1,
                1,
                10, TimeUnit.SECONDS,
                new SynchronousQueue<>(),
                new PrefixAbleThreadFactory("GlobalKeyService"),
                new SimpleRejectedHandler("热键监听器"));
        try {
            robot = new Robot();
        } catch (AWTException e) {
            DialogHelper.confirm("自定义快捷键模块:初始化失败").error().show();
        }
    }

    private boolean ignoreAction() {
        long cur = System.currentTimeMillis();
        return lastActTime != null && cur - lastActTime < minActSpan;
    }

    private void doCheckKeys() {
        if (KeyboardUtils.isPressed(KeyEvent.VK_ESCAPE) || isPressedCustomHotKey(MainConfig.HK_STOP)) {
            if (ignoreAction()) return;
            keyHandler.onEsc();
            lastActTime = System.currentTimeMillis();
        } else if (isPressedCustomHotKey(MainConfig.HK_SPEAK)) {
            if (ignoreAction()) return;
            if (robot != null) {
                //模拟按下:ctrl+c,取词
                robot.keyPress(KeyEvent.VK_CONTROL);
                robot.keyPress(KeyEvent.VK_C);
                robot.delay(20);
                robot.keyRelease(KeyEvent.VK_CONTROL);
                robot.keyRelease(KeyEvent.VK_C);
                robot.delay(20);
            }
            //取词后代码同下
            keyHandler.onCtrlC();
            lastActTime = System.currentTimeMillis();
        } else if (KeyboardUtils.isPressed(KeyEvent.VK_CONTROL) && KeyboardUtils.isPressed(KeyEvent.VK_C)) {
            if (ignoreAction()) return;
            keyHandler.onCtrlC();
            lastActTime = System.currentTimeMillis();
        }
    }

    /**
     * @return 是否按下了config中的自定义快捷键
     */
    private boolean isPressedCustomHotKey(String keyId) {
        ArrayList<Integer> keys = mainConfig.mapHotKey.get(keyId);
        if (ListUtil.nullOrEmpty(keys)) return false;
        for (Integer key : keys) if (!KeyboardUtils.isPressed(key)) return false;
        return true;
    }

    public void startListen() {
        if (keyHandler == null) throw new IllegalArgumentException("未配置keyHandler");

        listening = true;
        int activeCount = executor.getActiveCount();
        if (activeCount == 0) {
            log.debug("创建监听线程");
            executor.execute(() -> {
                while (listening) {
                    try {
                        doCheckKeys();
                        Thread.sleep(CHECK_KEY_SPAN);
                    } catch (InterruptedException e) {
                        log.error("InterruptedException", e);
                        Thread.currentThread().interrupt();
                        break;
                    }
                }
                log.debug("线程结束");
            });
        } else {
            log.info("复用已有监听线程");
        }
    }

    public void stopListen() {
        listening = false;
    }

    public void doTerminate() {
        listening = false;
        executor.shutdown();
    }

    public interface KeyHandler {
        void onEsc();

        void onCtrlC();
    }

    /**
     * 解决JNA无处理"特殊按键"的问题
     * 源码: com.sun.jna.platform.KeyboardUtils.W32KeyboardUtils
     * 键值对照: winuser.h
     * 平台不通用,仅针对windows
     */
    private interface KeyboardUtils {

        static int toNative(int code, int loc) {
            if ((code >= KeyEvent.VK_A && code <= KeyEvent.VK_Z)
                    || (code >= KeyEvent.VK_0 && code <= KeyEvent.VK_9)) {
                return code;
            }
            if (code == KeyEvent.VK_SHIFT) {
                if ((loc & KeyEvent.KEY_LOCATION_RIGHT) != 0) {
                    return WinUser.VK_RSHIFT;
                }
                if ((loc & KeyEvent.KEY_LOCATION_LEFT) != 0) {
                    return WinUser.VK_LSHIFT;
                }
                return WinUser.VK_SHIFT;
            }
            if (code == KeyEvent.VK_CONTROL) {
                if ((loc & KeyEvent.KEY_LOCATION_RIGHT) != 0) {
                    return WinUser.VK_RCONTROL;
                }
                if ((loc & KeyEvent.KEY_LOCATION_LEFT) != 0) {
                    return WinUser.VK_LCONTROL;
                }
                return WinUser.VK_CONTROL;
            }
            if (code == KeyEvent.VK_ALT) {
                if ((loc & KeyEvent.KEY_LOCATION_RIGHT) != 0) {
                    return WinUser.VK_RMENU;
                }
                if ((loc & KeyEvent.KEY_LOCATION_LEFT) != 0) {
                    return WinUser.VK_LMENU;
                }
                return WinUser.VK_MENU;
            }
            /*
            只改了这里!
            此处粗略对比了java.awt.event.KeyEvent.VK_CANCEL和winuser.h
            会有部分code是不一致的,如tab键,但很多键都一致[F1~12,数字键盘,insert等]
             */
            return code;
        }

        static boolean isPressed(int keyCode) {
            return isPressed(keyCode, KeyEvent.KEY_LOCATION_UNKNOWN);
        }

        static boolean isPressed(int keyCode, int location) {
            User32 lib = User32.INSTANCE;
            return (lib.GetAsyncKeyState(toNative(keyCode, location)) & 0x8000) != 0;
        }
    }

}
